#include "Bucket.h"
#include "../../Bytes.h"
#include "../Elemento.h"
#include "../../FuncionesUtiles.h"
#include "Directorio.h"
#include "../../claves/Key.h"
#include "../../componentes/RegistroVariable.h"
#include "../TipoClaveElemento.h"

Bucket::Bucket(cantBytes tamanio, Directorio* directorio, unsigned profundidad): Bloque(tamanio) {
	this->directorio = directorio;
	this->profundidad = profundidad;
	this->usados = 4;
}

Bucket::~Bucket() {

}

bool Bucket::obtenerElemento(Key &clave,Elemento &elemento) {

	Bytes bytesElemento = this->obtener(clave.serializarse(),this->directorio->extractor);

	if (bytesElemento.getTamanio() == 0) {
		return false;
	}

	Key aux(NULL);

	elemento.hidratarse(bytesElemento,this->directorio->extractor);
	return true;
}

bool Bucket::agregar(Elemento &elemento) {

	Key clave = elemento.getClave();

	Elemento elementoAux;

	/* Si la clave ya existia devuelvo false */
	if (obtenerElemento(clave,elementoAux)) {
		return false;
	}

	Bytes serializacionElemento = elemento.serializarse();
	RegistroVariable *registro = new RegistroVariable(elemento.serializarse());
	registro->setDato(elemento.serializarse());

	if (tieneLugar(registro)) {
		agregarRegistro(registro);
		this->directorio->guardarBucket(this);
	} else {
		delete registro;
		this->dividirse();
		return this->directorio->insertar(elemento);
	}


	return true;
}

bool Bucket::dividirse() {

	/* Si su profundidad es igual a la del directorio no puede dividirse */
	if (this->profundidad == this->directorio->profundidad) {
		this->directorio->duplicarTamanio();
	}

	Bucket* nuevo = new Bucket(this->directorio->tamanioBucket,this->directorio,this->profundidad + 1);

	offset inicial,final;
	this->direccionesNuevoBucket(inicial,final);

	this->directorio->insertarBucket(nuevo,inicial,final);

	this->profundidad++;
	this->directorio->guardarBucket(this);

	this->redistribuir(nuevo);

	this->directorio->guardarBucket(this);
	this->directorio->guardarBucket(nuevo);

	delete nuevo;
	return true;
}

bool Bucket::remover(Key &clave) {

	/* necesito obtener la direccion del hermano antes de borrar la clave
	 * porque si no tengo clave no puedo encontrar la direccion del bucket */
	obtenerDireccion();

	bool exito = eliminar(clave.serializarse(),this->directorio->extractor);

	if (exito) {
		directorio->guardarBucket(this);
		combinar();
	}

	return exito;
}

Bytes Bucket::serializarse() {

	Bytes serializacion;
	serializacion.agregarAlFinal(&this->profundidad, sizeof(unsigned));
	Bytes serializacionBloque = Bloque::serializarse();
	serializacion.agregarAlFinal(serializacionBloque);

	return serializacion;
}

void Bucket::hidratarse(const Bytes& bytes) {

	int tamanioProfundidad = sizeof(unsigned);
	this->profundidad = bytesToCantBytes(bytes.getSubBytes(0,tamanioProfundidad));
	Bloque::hidratarse(bytes.getSubBytes(tamanioProfundidad,bytes.getTamanio()- tamanioProfundidad));
	this->usados += 4;
	this->tamanio += 4;
}



void Bucket::redistribuir(Bucket* otroBucket) {

	list<Componente*>::iterator it =  componentes.begin();

	list<Componente*> listaNuevo;
	list<Componente*> listaViejo;

	for ( ; it != componentes.end(); it++){

		RegistroVariable* registro = (RegistroVariable*)(*it);
		Elemento elemento;

		elemento.hidratarse(registro->getDato(),this->directorio->extractor);
		Key clave = elemento.getClave();

		offset direccion = this->directorio->obtenerDireccionBucket(clave, this->directorio->profundidad);

		if (this->directorio->direcciones[direccion] != this->nroComponente) {
			listaNuevo.push_back(*it);
			otroBucket->usados += registro->getTamanioSerializado();
			this->usados -= registro->getTamanioSerializado();
		} else {
			listaViejo.push_back(*it);
		}
	}

	this->componentes = listaViejo;
	otroBucket->componentes = listaNuevo;

}

void Bucket::direccionesNuevoBucket(offset& inicial, offset& final) {

	offset direccionEnComun = obtenerDireccion();
	int bitsARellenar = directorio->profundidad - ( this->profundidad + 1);

	inicial = ( direccionEnComun << 1) | 1;
	final = inicial;

	for (int i = 0; i < bitsARellenar; i++) {
		inicial = inicial << 1;
		final = (final << 1) | 1;
	}

}

offset Bucket::obtenerDireccion() {

	list<Componente*>::iterator it = this->componentes.begin();

	if (it == componentes.end()) {
		return this->direccion;
	}

	RegistroVariable* reg = (RegistroVariable*)*it;
	Elemento elemento;
	elemento.hidratarse(reg->getDato(), directorio->extractor);
	Key clave = elemento.getClave();

	this->direccion = this->directorio->obtenerDireccionBucket(clave, this->profundidad);
	return this->direccion;
}

void Bucket::imprimirClaves() {

	if (componentes.size() == 0) {
		cout << "No tiene claves" << endl;
		return;
	}

	list<Componente*>::iterator it = componentes.begin();

	cout << "{";
	unsigned i = 0;

	for ( ; it != componentes.end(); it++) {
		i++;
		RegistroVariable* reg = (RegistroVariable*)(*it);
		Elemento elemento;
		elemento.hidratarse(reg->getDato(),directorio->extractor);
		cout << elemento.getClave().toString();

		if (i != componentes.size()) {
			cout << ",";
		}
	}
	cout << "}" << endl;
}


bool Bucket::tieneHermano() {

	if (directorio->profundidad == 1) {
		return false;
	}

	if (this->profundidad < directorio->profundidad) {
		return false;
	}

	return true;
}

offset Bucket::obtenerDireccionHermano() {
	return (obtenerDireccion() ^ 1);
}

bool Bucket::combinar() {

	if (tieneHermano()) {

	} else {

		return false;
	}

	offset direccionHermano = obtenerDireccionHermano();
	offset nroComponenteHermano = directorio->direcciones[direccionHermano];
	Bucket* hermano = directorio->obtenerBucketdeArchivo(nroComponenteHermano);

	list<Componente*> listaVacia;

	// si se puede combinar
	if ((this->usados + hermano->usados - 4) <= tamanio ) {
		list<Componente*>::iterator itHermano = hermano->componentes.begin();
		while (itHermano != hermano->componentes.end()) {
			RegistroVariable* registro = (RegistroVariable*)(*itHermano);
			agregarRegistro(registro);
			itHermano++;
		}
		hermano->componentes = listaVacia;
		hermano->usados = 4;
		directorio->direcciones[direccionHermano] = this->nroComponente;
		this->profundidad--;
		directorio->guardarBucket(this);

	} else {
		return false;
	}

	delete hermano;

	bool seRedujo = directorio->reducirTamanio();

	if (seRedujo) {
		combinar();
	}

	return false;
}
